<!DOCTYPE html>
<html lang="en">
	<head>
		<title>A*寻路算法(threeJS)使用多view绘图实现小地图功能</title>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
	</head>
	<body>

		<script type="text/javascript" src="libs/three.js"></script>
		<script type="text/javascript" src="libs/Detector.js"></script>
		<script type="text/javascript" src="libs/OrbitControls.js"></script>
		<script type="text/javascript" src="libs/astar.js"></script>
		<div id="ThreeJS" style="position: absolute; left:0px; top:0px"></div>

		<script>
			//容器,场景,相机,渲染器,控制器
			var container, scene, camera, renderer, controls;
			//屏幕宽度,屏幕高度
			var SCREEN_WIDTH = window.innerWidth,
				SCREEN_HEIGHT = window.innerHeight;
			// 视角,窗口宽高比,近裁截面,远裁截面.   aspect表示渲染窗口的长宽比，如果一个网页上只有一个全屏的canvas画布且画布上只有一个窗口，那么aspect的值就是网页窗口客户区的宽高比
			//near属性表示的是从距离相机多远的位置开始渲染，一般情况会设置一个很小的值。0.1
			//far属性表示的是距离相机多远的位置截止渲染，如果设置的值偏小，会有部分场景看不到	1000
			var VIEW_ANGLE = 45,
				ASPECT = SCREEN_WIDTH / SCREEN_HEIGHT,
				NEAR = 0.1,
				FAR = 20000;
			var windowWidth, windowHeight;
			var length = 200,
				flex = 2;
			var graph = [];
			var views = [{
					left: 0,
					top: 0,
					width: 1,
					height: 1,
					//background: new THREE.Color( 0.5, 0.5, 0.7 ),
					eye: [0, 200, 250],
					up: [0, 1, 0],
					fov: 45,
					updateCamera: function(camera, scene) {
						//					  camera.position.x += mouseX * 0.05;
						//					  camera.position.x = Math.max( Math.min( camera.position.x, 2000 ), -2000 );
						camera.lookAt(scene.position); //设置相机方向(指向的场景对象)
					}
				},
				{
					left: 0.75,
					top: 0.7,
					width: 0.3,
					height: 0.3,
					background: new THREE.Color(0.7, 0.5, 0.5),
					eye: [0, 200, 0],
					up: [0, 1, 0],
					fov: 60,
					updateCamera: function(camera, scene) {
						//					  camera.position.x -= mouseX * 0.05;
						//					  camera.position.x = Math.max( Math.min( camera.position.x, 2000 ), -2000 );
						camera.lookAt(camera.position.clone().setY(0));
					}
				},
			];
			threeStart();
			//启动
			function threeStart() {
				initThree();
				initScene();
				initCamera();
				initLight();
				initGround();
				initGrid();
				initControl();
				renderer.clear();
				animate();
			}
			//初始化Three
			function initThree() {

				if (Detector.webgl)
					renderer = new THREE.WebGLRenderer({
						antialias: true
					});
				else
					renderer = new THREE.CanvasRenderer();
				renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
				container = document.getElementById('ThreeJS');
				container.appendChild(renderer.domElement);

			}
			//初始化相机
			function initCamera() {
				//fov表示视场，所谓视场就是能够看到的角度范围，人的眼睛大约能够看到180度的视场，视角大小设置要根据具体应用，一般游戏会设置60~90度
				var view = views[0];
				camera = new THREE.PerspectiveCamera(view.fov, ASPECT, NEAR, FAR); //透视投影相机
				camera.position.set(0, 200, 250); //设置相机位置
				camera.lookAt(scene.position);
				view.camera = camera;
				var view2 = views[1];
				camera = new THREE.PerspectiveCamera(view2.fov, ASPECT, NEAR, FAR);
				camera.position.fromArray(view2.eye);
				camera.up.fromArray(view2.up);
				view2.camera = camera;
			}
			//初始化场景
			function initScene() {
				scene = new THREE.Scene();
			}
			//初始化光
			function initLight() {
				var ambientLight = new THREE.AmbientLight(0x111111);
				scene.add(ambientLight);
				var ambientLight = new THREE.AmbientLight(0x111111);
				scene.add(ambientLight);
			}

			//播放动画请去掉注释
			var isMoved;
			var isMoved2;
			var i;
			var geometry;
			var line, line2, rota;

			function groundAnimate() {

				if (i > length / 10) {
					return;
				}
				if (!isMoved) {
					scene.add(line);
					isMoved = true;
				}
				if (line.position.z > (i * 10) - length / 2) {
					line.position.z -= 2;
				} else if (line.position.z < (i * 10) - length / 2) {
					line.position.z += 2;
				} else {
					i++;
					isMoved = false;
					isMoved2 = false;
					line = new THREE.Line(geometry, new THREE.LineBasicMaterial({
						color: 0x808080,
						opacity: 1
					}));
					//var line2=new THREE.Line( geometry, new THREE.LineBasicMaterial( { color: 0x808080, opacity: 1 } ) );
					if (!isMoved2) {
						scene.add(line2);
						isMoved2 = true;
					}
					if (line2.position.x < (i * 10) - length / 2) {
						line2.position.x += 1;
					} else if (line2.position.x > (i * 10) - length / 2) {
						line2.position.x -= 1;
					} else {
						if (line2.rotation.y < 90 * Math.PI / 180) {
							line2.rotation.y += 10 * Math.PI / 180;
						} else if (line2.rotation.y > 90 * Math.PI / 180) {
							line2.rotation.y -= 10 * Math.PI / 180;
						} else {
							i++;
							isMoved = false;
							isMoved2 = false;
							line = new THREE.Line(geometry, new THREE.LineBasicMaterial({
								color: 0x808080,
								opacity: 1
							}));
							line2 = new THREE.Line(geometry, new THREE.LineBasicMaterial({
								color: 0x808080,
								opacity: 1
							}));
						}


					}
				}

				requestAnimationFrame(groundAnimate);

			}

			function initGround() {
				//播放动画请去掉注释
				// lines
				//isMoved=false;
				//isMoved2=false;
				//i=0;
				//line = new THREE.Line( geometry, new THREE.LineBasicMaterial( { color: 0x808080, opacity: 1 } ) );
				//line2 = new THREE.Line( geometry, new THREE.LineBasicMaterial( { color: 0x808080, opacity: 1 } ) )
				//groundAnimate()

				geometry = new THREE.Geometry();
				geometry.vertices.push(new THREE.Vector3(-length / 2, 0, 0));
				geometry.vertices.push(new THREE.Vector3(length / 2, 0, 0));

				//播放动画请注释一下内容
				for (var i = 0; i <= length / 10; i++) {

					var line = new THREE.Line(geometry, new THREE.LineBasicMaterial({
						color: 0x808080,
						opacity: 1
					}));
					line.position.z = (i * 10) - length / 2;
					scene.add(line);

					var line = new THREE.Line(geometry, new THREE.LineBasicMaterial({
						color: 0x808080,
						opacity: 1
					}));
					line.position.x = (i * 10) - length / 2;
					line.rotation.y = 90 * Math.PI / 180;
					scene.add(line);

				}
				//播放动画请注释以上内容
				var geometry = new THREE.PlaneGeometry(length, 10);
				var material = new THREE.MeshBasicMaterial({
					color: 0x808080,
					side: THREE.DoubleSide
				});
				var plane = new THREE.Mesh(geometry, material);
				plane.position.set(0, 5, length / 2);
				scene.add(plane);
				var plane = new THREE.Mesh(geometry, material);
				plane.rotation.y = 90 * Math.PI / 180;
				plane.position.set(length / 2, 5, 0);
				scene.add(plane);
				var plane = new THREE.Mesh(geometry, material);
				plane.position.set(0, 5, -length / 2);
				scene.add(plane);
				var plane = new THREE.Mesh(geometry, material);
				plane.rotation.y = 90 * Math.PI / 180;
				plane.position.set(-length / 2, 5, 0);
				scene.add(plane);
				var skyBoxGeometry = new THREE.CubeGeometry(10000, 10000, 10000);
				var skyBoxMaterial = new THREE.MeshBasicMaterial({
					color: 0xFFFFFF,
					side: THREE.BackSide
				});
				var skyBox = new THREE.Mesh(skyBoxGeometry, skyBoxMaterial);
				scene.add(skyBox);

			}
			//初始化控制
			function initControl() {
				controls = new THREE.OrbitControls(views[0].camera, renderer.domElement); //轨道控制
			}

			//初始化网格
			function initGrid() {
				var geometry = new THREE.CubeGeometry(8, 8, 8); //立方体几何
				var material = new THREE.MeshBasicMaterial({
					color: 0xC0C0C0
				});
				for (var i = 0; i < length / 10; i++) {
					var nodeRow = [];
					graph.push(nodeRow);
					for (var j = 0; j < length / 10; j++) {
						var salt = randomNum(1, 7);
						if (salt > flex) {
							nodeRow.push(1);
						} else {
							nodeRow.push(0);
						}
						if (salt <= flex) {
							var cube = new THREE.Mesh(geometry, material);
							cube.position.set(10 * j - length / 2 + 5, 5, 10 * i - length / 2 + 5);
							scene.add(cube);
						}
					}
				}
				// console.log(graph)
			}

			var resultArray = new Array();


			//拾取对象
			function pickupObjects(e) {
				var raycaster = new THREE.Raycaster();
				let mouse = new THREE.Vector2();
				var fxl = new THREE.Vector3(0, 1, 0);
				var groundplane = new THREE.Plane(fxl, 0);
				mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
				mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
				//从相机发射一条射线，经过鼠标点击位置
				raycaster.setFromCamera(mouse, views[0].camera);
				//计算射线相机到的对象，可能有多个对象，因此返回的是一个数组，按离相机远近排列
				var ray = raycaster.ray;
				let intersects = ray.intersectPlane(groundplane);
				let x = intersects.x;
				let z = intersects.z;

				if (Math.abs(x) > length / 2 || Math.abs(z) > (length / 2)) {
					return;
				}

				var k, m;
				for (var i = -length / 2; i < length / 2; i += 10) {
					if (x >= i && x < i + 10) {
						k = i + 5;
					}
				}
				for (var j = -length / 2; j < length / 2; j += 10) {
					if (z >= j && z < j + 10) {
						m = j + 5;
					}
				}

				initSphere(k, m); //初始化球体
			}

			document.addEventListener('click', pickupObjects, false); //监听单击拾取对象初始化球体
			var isCaculate = false;

			//清除小球
			function cleanSphere() {
				let child = scene.children; //获取场景中的所有子对象
				for (var i = 0; i < child.length; i++) {
					if (child[i].geometry instanceof THREE.SphereGeometry) { //几何对象是球体几何
						scene.remove(child[i]); //从场景中移除
						i--;
					}
				}
				isCaculate = false;
			}

			//初始球体
			function initSphere(x, z) {
				if (isCaculate) {
					cleanSphere();
				}
				var geometry = new THREE.SphereGeometry(5, 32, 32); //球体几何
				var material = new THREE.MeshBasicMaterial({
					color: 0xffff00
				}); //网格基础材料

				if (resultArray.length == 0) {
					var sphere = new THREE.Mesh(geometry, material);
					sphere.position.x = x;
					sphere.position.y = 5;
					sphere.position.z = z;
					resultArray.push(sphere);
					scene.add(sphere);
				} else if (resultArray[0].position.x != x && resultArray[0].position.z != z) {
					var sphere = new THREE.Mesh(geometry, material);
					sphere.position.x = x;
					sphere.position.y = 5;
					sphere.position.z = z;
					resultArray.push(sphere);
					scene.add(sphere);
					caculatePath(resultArray);
					isCaculate = true;
					// console.log("起始点坐标为  x:"+resultArray[0].position.x+",z:"+resultArray[0].position.z);
					// console.log("终止点坐标为  x:"+resultArray[1].position.x+",z:"+resultArray[1].position.z);
					resultArray = new Array();
				}

			}

			var result;

			//计算路径
			function caculatePath(resultArray) {
				// console.log(resultArray);
				var maps = new Graph(graph); //地图
				var startX = (resultArray[0].position.z - 5 + length / 2) / 10;
				var startY = (resultArray[0].position.x - 5 + length / 2) / 10;
				var endX = (resultArray[1].position.z - 5 + length / 2) / 10;
				var endY = (resultArray[1].position.x - 5 + length / 2) / 10;
				//console.log(startX+' '+startY+' '+endX+' '+endY);
				var start = maps.grid[startX][startY];
				var end = maps.grid[endX][endY];
				result = astar.search(maps, start, end);
				if (result.length == 0) {
					alert("无可到达路径");
					cleanSphere();
					return;
				}
				//	    var timesRun = 0;
				timesRun = 0;
				tempSphere = resultArray[0];
				pathAnimate();
				//		var interval = setInterval(function(){
				//			var geometry = new THREE.SphereGeometry( 5, 32, 32 );
				//			var material = new THREE.MeshBasicMaterial( {color: 0xffff00} );
				//			var sphere = new THREE.Mesh( geometry, material );
				//			sphere.position.x=result[timesRun].y*10-length/2+5;
				//			sphere.position.y=5;
				//			sphere.position.z=result[timesRun].x*10-length/2+5;
				//			scene.add( sphere );
				//			timesRun += 1;
				//			if(timesRun == result.length){
				//			    clearInterval(interval);
				//			    }
				//			}, 200);

			}

			var timesRun = 0;
			var tempSphere;
			var isAdded = false;

			//路径动画
			function pathAnimate() {
				if (!isAdded) {
					scene.add(tempSphere);
					isAdded = true;
				}
				if (timesRun == result.length) {
					return;
				}
				var next = {
					x: result[timesRun].y * 10 - length / 2 + 5,
					z: result[timesRun].x * 10 - length / 2 + 5
				}
				if (tempSphere.position.x == next.x && tempSphere.position.z == next.z) {
					timesRun++;
					requestAnimationFrame(pathAnimate);
					return;
				}
				if (tempSphere.position.x > next.x) tempSphere.position.x -= 1;
				if (tempSphere.position.x < next.x) tempSphere.position.x += 1;
				if (tempSphere.position.z > next.z) tempSphere.position.z -= 1;
				if (tempSphere.position.z < next.z) tempSphere.position.z += 1;
				requestAnimationFrame(pathAnimate);

			}

			function randomNum(minNum, maxNum) {
				switch (arguments.length) {
					case 1:
						return parseInt(Math.random() * minNum + 1, 10);
						break;
					case 2:
						return parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10);
						break;
					default:
						return 0;
						break;
				}
			}

			function updateSize() {
				if (windowWidth != window.innerWidth || windowHeight != window.innerHeight) {
					windowWidth = window.innerWidth;
					windowHeight = window.innerHeight;
					renderer.setSize(windowWidth, windowHeight);
				}
			}

			function animate() {
				render();
				controls.update();
				requestAnimationFrame(animate);
			}

			//渲染
			function render() {
				updateSize();
				for (var ii = 0; ii < views.length; ++ii) {
					var view = views[ii];
					var camera = view.camera;
					view.updateCamera(camera, scene);
					var left = Math.floor(windowWidth * view.left);
					var top = Math.floor(windowHeight * view.top);
					var width = Math.floor(windowWidth * view.width);
					var height = Math.floor(windowHeight * view.height);
					renderer.setViewport(left, top, width, height);
					renderer.setScissor(left, top, width, height);
					renderer.setScissorTest(true);
					renderer.setClearColor(view.background);
					camera.aspect = width / height;
					camera.updateProjectionMatrix(); //更新投影矩阵
					renderer.render(scene, camera); //更新渲染器
				}
			}
		</script>

	</body>
</html>
